package gpstools.data;

import generated.gpx.GpxType;
import generated.gpx.TrackPointExtensionT;
import generated.gpx.TrkType;
import generated.gpx.TrksegType;
import generated.gpx.WptType;
import gpstools.track.Track;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

/**
 * GPX Loader
 * 
 * @author Marcin Skruch
 */
public class GpxLoader implements Loader, TrackSource {
    private static final String EXTENSION = "gpx";
    private Queue<Track> tracks = new LinkedList<Track>();

    GpxLoader() {
    }

    @Override
    public String getExtension() {
        return EXTENSION;
    }

    public TrackSource load(String fileName) throws IOException, UnsupportedFormatException {
        return new GpxLoader(fileName);
    }
    
    @Override
    public TrackSource load(byte[] data) throws UnsupportedFormatException {
        return new GpxLoader(data);
    }

    public GpxLoader(byte[] data) throws UnsupportedFormatException {
        this(new ByteArrayInputStream(data));
    }

    public GpxLoader(String fileName) throws FileNotFoundException, UnsupportedFormatException {
        this(new FileInputStream(fileName));
    }

    public GpxLoader(InputStream inputStream) throws UnsupportedFormatException {
        final GpxType gpx = unmarshal(inputStream).getValue();
        if (gpx == null || gpx.getTrk() == null) {
            throw new UnsupportedFormatException("no data in the gpx file");
        }

        for (TrkType gpxTrack : gpx.getTrk()) {
            Track.Builder builder = Track.builder().name(gpxTrack.getName());
            for (TrksegType gpxLap : gpxTrack.getTrkseg()) {
                List<WptType> gpxPoints = gpxLap.getTrkpt();
                if (!gpxPoints.isEmpty()) {
                    for (WptType gpxPoint : gpxPoints) {
                        builder.add(GpxConverter.pointFromGpx(gpxPoint));
                    }
                    builder.closeLap();
                }
            }
            if (!builder.isEmpty()) {
                tracks.add(builder.build());
            }
        }
    }

    private static JAXBElement<GpxType> unmarshal(InputStream input) throws UnsupportedFormatException {
        try {
            JAXBContext context = JAXBContext.newInstance(GpxType.class, TrackPointExtensionT.class);
            Unmarshaller unmarshaller = context.createUnmarshaller();
            @SuppressWarnings("unchecked")
            JAXBElement<GpxType> root = (JAXBElement<GpxType>) unmarshaller.unmarshal(input);
            return root;
        } catch (JAXBException e) {
            throw new UnsupportedFormatException(e);
        }
    }

    @Override
    public Track next() {
        return tracks.poll();
    }
    
    public List<Track> all() {
        ArrayList<Track> list = new ArrayList<Track>();
        Track track;
        while ((track = next()) != null) {
            list.add(track);
        }
        return list;
    }
}
